#include "pch.hpp"
#include "D3D11ConstantsBuffer.hpp"
#include "D3D11Exception.hpp"

using namespace Engine;
using namespace Engine::Video;




D3D11ConstantsBuffer::D3D11ConstantsBuffer(const notify& destroyedFn, uint32 size, const StaticReflection& bufferReflection, Buffer::Type usageType)
	: m_destroyed(destroyedFn)
	, m_changed(true)
	, m_bufferReflection(bufferReflection)
	, m_usageType(usageType)
	, m_bufferSize(0)
	, m_size(size)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

void D3D11ConstantsBuffer::post_create(ptr _this)
{
	m_this = _this;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

D3D11ConstantsBuffer::~D3D11ConstantsBuffer()
{
	m_destroyed(m_this);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Buffer::Type D3D11ConstantsBuffer::usage() const
{
	return m_usageType;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

StaticReflection D3D11ConstantsBuffer::reflect() const
{
	return m_bufferReflection;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




const ID3D11BufferPtr& D3D11ConstantsBuffer::apply(const ID3D11DevicePtr& device, const ID3D11DeviceContextPtr& context)
{
	updateBuffer(device, context);

	return m_buffer;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void D3D11ConstantsBuffer::update(const void* data, uint32 dataSize)
{
	if(0 == dataSize)
		return;

	if(m_size != dataSize)
		BOOSTEXT_THROW(programming_error("A ConstantsBuffer is bound to the InputLayout it has been created with and thus it's stride size is expected to stay constant"));

	// Due to limitations of the d3d pipeline, a constants buffer must be the multiple of 16 bytes
	// Thus we add a padding to the buffer
	uint32 bufferSize = (dataSize % 16) + dataSize;
	m_tmpBuffer.resize(bufferSize);
	memcpy(&m_tmpBuffer[0], data, dataSize);

	m_changed = true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void D3D11ConstantsBuffer::updateBuffer(const ID3D11DevicePtr& device, const ID3D11DeviceContextPtr& context)
{
	if(!m_changed)
		return;

	if(0 == m_tmpBuffer.size())
		return;

	if(m_tmpBuffer.size() > m_bufferSize)
	{
		D3D11_BUFFER_DESC cbDesc;
		cbDesc.ByteWidth = boost::numeric_cast<uint32>(m_tmpBuffer.size());
		cbDesc.Usage = usageFromType(m_usageType);
		cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		cbDesc.CPUAccessFlags = cpuAccessFlags(m_usageType);
		cbDesc.MiscFlags = 0;
		cbDesc.StructureByteStride = 0;

		D3D11_SUBRESOURCE_DATA sub;
		ZeroMemory(&sub, sizeof(sub));
		sub.pSysMem = &m_tmpBuffer[0];

		// Create the buffer.
		ID3D11Buffer* buffer;
		D3D11_API(device->CreateBuffer(&cbDesc, &sub, &buffer));
		m_buffer     = ID3D11BufferPtr(buffer);
		m_bufferSize = boost::numeric_cast<uint32>(m_tmpBuffer.size());
	}
	else
	{
		switch(m_usageType)
		{
		case Buffer::SemiDynamic:

			D3D11_BOX box;
			ZeroMemory(&box, sizeof(box));
			box.right = boost::numeric_cast<uint32>(m_tmpBuffer.size());
			box.bottom = 1;
			box.back = 1;

			context->UpdateSubresource(m_buffer.get(), 0, &box, &m_tmpBuffer[0], 0, 0);

			break;
		case Buffer::Dynamic:

			D3D11_MAPPED_SUBRESOURCE res;
			D3D11_API(context->Map(m_buffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &res));
			memcpy(res.pData, &m_tmpBuffer[0], m_tmpBuffer.size());
			context->Unmap(m_buffer.get(), 0);

			break;
		default:
			BOOSTEXT_THROW(invalid_value_exception(m_usageType));
		}
	}

	m_changed = false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void* D3D11ConstantsBuffer::data(uint32 dataSize)
{
	if(0 == dataSize)
		BOOSTEXT_THROW(programming_error("It is illegal to request a buffer of 0 size."));

	if(dataSize > m_tmpBuffer.size())
		BOOSTEXT_THROW(programming_error("The buffer is smaller than the requested size"));

	return &m_tmpBuffer[0];
}
///////////////////////////////////////////////////////////////////////////////////////////////////




D3D11_USAGE D3D11ConstantsBuffer::usageFromType(Buffer::Type usageType)
{
	switch(usageType)
	{
	case Buffer::Static:      return D3D11_USAGE_IMMUTABLE;
	case Buffer::SemiDynamic: return D3D11_USAGE_DEFAULT;
	case Buffer::Dynamic:     return D3D11_USAGE_DYNAMIC;
	default: BOOSTEXT_THROW(invalid_value_exception(usageType));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

UINT D3D11ConstantsBuffer::cpuAccessFlags(Buffer::Type usageType)
{
	switch(usageType)
	{
	case Buffer::Static:
		return 0;
	case Buffer::SemiDynamic:
	case Buffer::Dynamic:
		return D3D11_CPU_ACCESS_WRITE;
	default:
		BOOSTEXT_THROW(invalid_value_exception(usageType));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
